#include <stx/test/test.hpp>

TEST_CASE (core_http)
{
    auto hdr_dict = HttpHeaderDict{};

    hdr_dict["Content-Length"s] = "12345"s;
    hdr_dict["Content-Type"s]   = "text/json"s;

    CHECK("Content-Length:12345|Content-Type:text/json"sv == tos(hdr_dict) ||
          "Content-Type:text/json|Content-Length:12345"sv == tos(hdr_dict));

    auto req_line =
        HttpRequestLine{.method = HttpMethod::K_GET, .uri = "/index"s};
    CHECK("K_GET|/index"sv == tos(req_line));

    auto status_line =
        HttpStatusLine{.status = HttpStatus(200), .reason = "OK"s};
    CHECK("200|OK"sv == tos(status_line));

    {
        auto req = HttpRequest{.req_line = req_line, .header_dict = hdr_dict};
        CHECK("K_GET|/index|Content-Length:12345|Content-Type:text/json"sv ==
                  tos(req) ||
              "K_GET|/index|Content-Type:text/json|Content-Length:12345"sv ==
                  tos(req));
    }

    {
        auto req = HttpRequest{.req_line    = req_line,
                               .header_dict = hdr_dict,
                               .body = Buffer{std::byte('o'), std::byte('k')}};
        CHECK("K_GET|/index|Content-Length:12345|Content-Type:text/json|ok"sv ==
                  tos(req, HttpBodyDumpOption::K_AS_TEXT) ||
              "K_GET|/index|Content-Type:text/json|Content-Length:12345|ok"sv ==
                  tos(req, HttpBodyDumpOption::K_AS_TEXT));
    }

    {
        auto req =
            HttpRequest{.req_line    = req_line,
                        .header_dict = hdr_dict,
                        .body = Buffer{std::byte(0xab), std::byte(0xcd)}};
        CHECK(
            "K_GET|/index|Content-Length:12345|Content-Type:text/json|abcd"sv ==
                tos(req, HttpBodyDumpOption::K_AS_OCTETS) ||
            "K_GET|/index|Content-Type:text/json|Content-Length:12345|abcd"sv ==
                tos(req, HttpBodyDumpOption::K_AS_OCTETS));
    }

    {
        auto rsp =
            HttpResponse{.status_line = status_line, .header_dict = hdr_dict};
        CHECK("200|OK|Content-Length:12345|Content-Type:text/json"sv ==
                  tos(rsp) ||
              "200|OK|Content-Type:text/json|Content-Length:12345"sv ==
                  tos(rsp));
    }

    {
        auto rsp = HttpResponse{.status_line = status_line,
                                .header_dict = hdr_dict,
                                .body = Buffer{std::byte('o'), std::byte('k')}};
        CHECK("200|OK|Content-Length:12345|Content-Type:text/json|ok"sv ==
                  tos(rsp, HttpBodyDumpOption::K_AS_TEXT) ||
              "200|OK|Content-Type:text/json|Content-Length:12345|ok"sv ==
                  tos(rsp, HttpBodyDumpOption::K_AS_TEXT));
    }

    {
        auto rsp =
            HttpResponse{.status_line = status_line,
                         .header_dict = hdr_dict,
                         .body = Buffer{std::byte(0xab), std::byte(0xcd)}};
        CHECK("200|OK|Content-Length:12345|Content-Type:text/json|abcd"sv ==
                  tos(rsp, HttpBodyDumpOption::K_AS_OCTETS) ||
              "200|OK|Content-Type:text/json|Content-Length:12345|abcd"sv ==
                  tos(rsp, HttpBodyDumpOption::K_AS_OCTETS));
    }

    {
        auto msg =
            HttpMsg{.first_line = "first_line"s, .header_dict = hdr_dict};
        CHECK("first_line|Content-Length:12345|Content-Type:text/json"sv ==
                  tos(msg) ||
              "first_line|Content-Type:text/json|Content-Length:12345"sv ==
                  tos(msg));
    }

    {
        auto msg = HttpMsg{.first_line  = "first_line"s,
                           .header_dict = hdr_dict,
                           .body = Buffer{std::byte('o'), std::byte('k')}};
        CHECK("first_line|Content-Length:12345|Content-Type:text/json|ok"sv ==
                  tos(msg, HttpBodyDumpOption::K_AS_TEXT) ||
              "first_line|Content-Type:text/json|Content-Length:12345|ok"sv ==
                  tos(msg, HttpBodyDumpOption::K_AS_TEXT));
    }

    {
        auto msg = HttpMsg{.first_line  = "first_line"s,
                           .header_dict = hdr_dict,
                           .body = Buffer{std::byte(0xab), std::byte(0xcd)}};
        CHECK("first_line|Content-Length:12345|Content-Type:text/json|abcd"sv ==
                  tos(msg, HttpBodyDumpOption::K_AS_OCTETS) ||
              "first_line|Content-Type:text/json|Content-Length:12345|abcd"sv ==
                  tos(msg, HttpBodyDumpOption::K_AS_OCTETS));
    }

    {
        auto rsp =
            HttpResponse{.status_line = status_line, .header_dict = hdr_dict};
        auto cr = CurlResult{.status = 1, .cerr = {}, .rsp = rsp};
        CHECK("1||200|OK|Content-Length:12345|Content-Type:text/json"sv ==
                  tos(cr) ||
              "1||200|OK|Content-Type:text/json|Content-Length:12345"sv ==
                  tos(cr));
    }

    {
        auto rsp = HttpResponse{.status_line = status_line,
                                .header_dict = hdr_dict,
                                .body = Buffer{std::byte('o'), std::byte('k')}};
        auto cr  = CurlResult{.status = 1, .cerr = {}, .rsp = rsp};
        CHECK("1||200|OK|Content-Length:12345|Content-Type:text/json|ok"sv ==
                  tos(cr, HttpBodyDumpOption::K_AS_TEXT) ||
              "1||200|OK|Content-Type:text/json|Content-Length:12345|ok"sv ==
                  tos(cr, HttpBodyDumpOption::K_AS_TEXT));
    }

    {
        auto rsp =
            HttpResponse{.status_line = status_line,
                         .header_dict = hdr_dict,
                         .body = Buffer{std::byte(0xab), std::byte(0xcd)}};
        auto cr = CurlResult{.status = 1, .cerr = {}, .rsp = rsp};
        CHECK("1||200|OK|Content-Length:12345|Content-Type:text/json|abcd"sv ==
                  tos(cr, HttpBodyDumpOption::K_AS_OCTETS) ||
              "1||200|OK|Content-Type:text/json|Content-Length:12345|abcd"sv ==
                  tos(cr, HttpBodyDumpOption::K_AS_OCTETS));
    }

    auto const opt_req_line = parseHttpRequestLine("GET / HTTP/1.1"sv);
    CHECK(opt_req_line);
    CHECK(HttpMethod::K_GET == opt_req_line->method);
    CHECK("/"sv == opt_req_line->uri);

    auto const opt_status_line = parseHttpStatusLine("HTTP/1.1 200 OK"sv);
    CHECK(opt_status_line);
    CHECK(HttpStatus(200) == opt_status_line->status);
    CHECK("OK"sv == opt_status_line->reason);

    auto const local_ip_obj = "127.0.0.1"_ip4;
    auto       local_ep     = Endpoint(local_ip_obj, Port(64003));

    auto const fn_on_http_req =
        [&](TcpSocket&            tcp_skt,
            HttpMsg               msg,
            RemoteEndpoint const& remote_ep,
            LocalEndpoint const&  local_ep) -> Future<FsmState>
    {
        CHECK(remote_ep.ip4() == local_ip_obj);
        CHECK(local_ep.ip4() == local_ip_obj);

        auto s_rsp = scat(msg.body.size(), _I_, msg.body.tosv());

        if (!("1|a"sv == s_rsp || s_rsp.size() > 3))
        {
            xout(s_rsp.size(), _I_, s_rsp);
            xout(msg.first_line);
            for (auto const& [key, value] : msg.header_dict)
            {
                xout(key, ":"sv, value);
            }
        }

        auto       hdr_dict = HttpHeaderDict{};
        auto const qr       = query(msg.header_dict, "Connection"sv);
        if (qr)
        {
            hdr_dict["Connection"s] = qr.mapped();
        }
        else
        {
            hdr_dict["Connection"s] = "close"s;
        }

        if ("close"sv == hdr_dict["Connection"s])
        {
            auto status = co_await sendHttpResponse(tcp_skt,
                                                    HttpStatus(200),
                                                    "OK"sv,
                                                    std::move(hdr_dict),
                                                    s_rsp,
                                                    5s);
            if (status <= 0)
            {
                LOG_ERROR(status);
                co_return FsmState::K_FAILURE;
            }

            co_return FsmState::K_SUCCESS;
        }

        auto status = co_await sendHttpResponse(
            tcp_skt, HttpStatus(200), "OK"sv, std::move(hdr_dict), s_rsp, 5s);
        if (status <= 0)
        {
            LOG_ERROR(status);
            co_return FsmState::K_FAILURE;
        }

        co_return FsmState::K_ACTIVE;
    };

    startHttpServer(local_ep, fn_on_http_req);
    sleepFor(1s);

    auto cnt1 = std::atomic_size_t(0);
    auto cnt2 = std::atomic_size_t(0);
    auto cnt3 = std::atomic_size_t(0);
    auto cnt4 = std::atomic_size_t(0);

    auto const fn_client = [&] -> Future<>
    {
        auto              tcp_skt = co_await startTcpClient(local_ep);
        static auto const hdr_dict =
            HttpHeaderDict{{"Connection"s, "keep-alive"s}};

        CHECK(tcp_skt);

        LOOP_N (3)
        {
            auto s_body = genRandString(10, 100);
            auto rsp    = co_await sendHttpRequest(
                tcp_skt, HttpMethod::K_POST, "/"sv, hdr_dict, s_body, 60s, {});
            if (HttpStatus(200) == rsp.status_line.status)
            {
                if (rsp.body.tosv() != scat(s_body.size(), _I_, s_body))
                {
                    xerr(
                        "fn_client err: "sv, _I_, rsp.body.tosv(), _I_, s_body);
                }
            }
            else
            {
                xerr("fn_client err: "sv, rsp.status_line);
                break;
            }
        }

        ++cnt1;
    };

    auto const fn_curl_client = [&] -> Future<>
    {
        static auto const hdr_dict = HttpHeaderDict{{"Connection"s, "close"s}};
        auto              s_body   = genRandString(10, 100);
        auto cr = co_await curl("127.0.0.1:64003"sv, 60s, hdr_dict, s_body);

        if (C_STATUS_SUCCESS == cr.status &&
            HttpStatus(200) == cr.rsp.status_line.status)
        {
            if (cr.rsp.body.tosv() != scat(s_body.size(), _I_, s_body))
            {
                xerr("fn_curl_client err: "sv,
                     _I_,
                     cr.rsp.body.tosv(),
                     _I_,
                     s_body);
            }
        }
        else
        {
            xerr("fn_curl_client err: "sv, cr.status, _I_, cr.rsp.status_line);
        }

        ++cnt2;
    };

    constexpr auto sv_data = "a"sv;

    auto const fn_client2 = [&] -> Future<>
    {
        auto              tcp_skt = co_await startTcpClient(local_ep);
        static auto const hdr_dict =
            HttpHeaderDict{{"Connection"s, "keep-alive"s}};

        CHECK(tcp_skt);

        LOOP_N (3)
        {
            auto rsp = co_await sendHttpRequest(
                tcp_skt, HttpMethod::K_POST, "/"sv, hdr_dict, sv_data, 60s, {});
            if (HttpStatus(200) == rsp.status_line.status)
            {
                if (rsp.body.tosv() != "1|a"sv)
                {
                    xerr("fn_client2 err: "sv, _I_, rsp.body.tosv());
                }
            }
            else
            {
                xerr("fn_client2 err: "sv, rsp.status_line);
                break;
            }
        }

        ++cnt3;
    };

    auto const fn_curl_client2 = [&] -> Future<>
    {
        static auto const hdr_dict = HttpHeaderDict{{"Connection"s, "close"s}};
        auto cr = co_await curl("127.0.0.1:64003"sv, 60s, hdr_dict, sv_data);

        if (C_STATUS_SUCCESS == cr.status &&
            HttpStatus(200) == cr.rsp.status_line.status)
        {
            if (cr.rsp.body.tosv() != "1|a"sv)
            {
                xerr("fn_curl_client2 err: "sv, _I_, cr.rsp.body.tosv());
            }
        }
        else
        {
            xerr("fn_curl_client2 err: "sv, cr.status, _I_, cr.rsp.status_line);
        }

        ++cnt4;
    };

    LOOP (i, 10uz)
    {
        LOOP_N (100)
        {
            fn_client();
        }

        LOOP_N (100)
        {
            fn_curl_client();
        }

        LOOP_N (10000)
        {
            fn_client2();
        }

        LOOP_N (1000)
        {
            fn_curl_client2();
        }

        while (cnt1 < (i + 1) * 100uz)
        {
            sleepFor(200ms);
        }

        while (cnt2 < (i + 1) * 100uz)
        {
            sleepFor(200ms);
        }

        while (cnt3 < (i + 1) * 10000uz)
        {
            sleepFor(200ms);
        }

        while (cnt4 < (i + 1) * 1000uz)
        {
            sleepFor(200ms);
        }

        cout(Tco::K_ERASE_CUR_LINE, "http request batch: "sv, i, Tco::K_RESET);
    }

    cout(Tco::K_ERASE_CUR_LINE, Tco::K_RESET);
}